<?php

namespace Xin\Hint;

use Exception;
use Xin\Capsule\Service;
use Xin\Hint\Contracts\HintHandler as HintContract;
use Xin\Hint\Contracts\Render;

class HintHandler extends Service implements HintContract
{

	/**
	 * @var Render
	 */
	protected $handler;

	/**
	 * @var callable
	 */
	protected $dataPreprocessor;

	/**
	 * @param array $config
	 * @param Render $handler
	 */
	public function __construct(array $config, Render $handler)
	{
		parent::__construct($config);

		$this->handler = $handler;

		if (method_exists($this->handler, 'setHintHandler')) {
			$this->handler->setHintHandler($this);
		}
	}

	/**
	 * @inheritDoc
	 */
	public function result($data = [], array $extend = [])
	{
		return $this->success($this->getSuccessMsg(), null, $data, $extend);
	}

	/**
	 * @inheritDoc
	 */
	public function success($msg, $url = null, $data = null, array $extend = [])
	{
		if ($url || !$this->handler->isAjax()) {
			$url = $this->successUrl($url);
			$extend['url'] = $url;
		}

		return $this->render($this->getSuccessCode(), $msg, $data, $extend);
	}

	/**
	 * @inheritDoc
	 */
	public function error($msg, $code = null, $url = null, array $extend = [])
	{
		if ($msg instanceof Exception) {
			$code = $msg->getCode();
			$msg = $msg->getMessage();
			$extend = is_array($code) ? $code : [];
		} elseif ($code === null) {
			$code = $this->getErrorCode();
		}

		$extend['url'] = (string)$url;

		return $this->render($code, $msg, null, $extend);
	}

	/**
	 * @inheritDoc
	 */
	public function alert($msg, $code = null, $url = null, array $extend = [])
	{
		return $this->error($msg, $code, $url, array_merge([
			'tips_type' => 'alert',
		], $extend));
	}

	/**
	 * make Response
	 *
	 * @param string $code
	 * @param string $msg
	 * @param mixed $data
	 * @param array $extend
	 * @return mixed
	 */
	protected function render($code, $msg, $data, array $extend = [])
	{
		if ($this->dataPreprocessor) {
			$data = call_user_func($this->dataPreprocessor, $data);
		}

		return $this->handler->render(
			array_merge([
				$this->getFormatterCodeField() => $code,
				$this->getFormatterMsgField() => $msg,
				$this->getFormatterDataField() => $data,
			], $extend),
			$this->config
		);
	}

	/**
	 * @inheritDoc
	 */
	public function outputSuccess($msg, $url = null, $data = null, array $extend = [], callable $callback = null)
	{
		$this->output(
			$this->success($msg, $url, $data, $extend),
			$callback
		);
	}

	/**
	 * @inheritDoc
	 */
	public function outputError($msg, $code = 0, $url = null, array $extend = [], callable $callback = null)
	{
		$this->output(
			$this->error($msg, $code, $url, $extend),
			$callback
		);
	}

	/**
	 * @inheritDoc
	 */
	public function outputAlert($msg, $code = 0, $url = null, array $extend = [], callable $callback = null)
	{
		$this->output(
			$this->alert($msg, $code, $url, $extend),
			$callback
		);
	}

	/**
	 * 直接输出
	 *
	 * @param mixed $response
	 * @param callable|null $callback
	 */
	protected function output($response, callable $callback = null)
	{
		$this->handler->output($response, $callback);
	}

	/**
	 * @param string $url
	 * @return string
	 */
	protected function successUrl($url)
	{
		if (is_null($url) && isset($_SERVER["HTTP_REFERER"])) {
			$url = $_SERVER["HTTP_REFERER"];
		} elseif ($url && method_exists($this->handler, 'parseUrl')) {
			$url = $this->handler->parseUrl($url);
		}

		return $url;
	}

	/**
	 * @param string $url
	 * @return string
	 */
	protected function errorUrl($url)
	{
		if (is_null($url)) {
			$url = $this->handler->isAjax() ? '' : 'javascript:history.back(-1);';
		} elseif ($url && method_exists($this->handler, 'parseUrl')) {
			$url = $this->handler->parseUrl($url);
		}

		return $url;
	}

	/**
	 * @return int
	 */
	protected function getErrorCode()
	{
		return $this->getConfig('error_code', 0);

	}

	/**
	 * @return int
	 */
	protected function getSuccessCode()
	{
		return $this->getConfig('success_code', 1);
	}

	/**
	 * @return string
	 */
	protected function getSuccessMsg()
	{
		return $this->getConfig('success_msg', 'OK');
	}

	/**
	 * @return string
	 */
	protected function getFormatterCodeField()
	{
		return $this->getConfig('formatter.code', 'code');
	}

	/**
	 * @return string
	 */
	protected function getFormatterMsgField()
	{
		return $this->getConfig('formatter.msg', 'msg');
	}

	/**
	 * @return string
	 */
	protected function getFormatterDataField()
	{
		return $this->getConfig('formatter.data', 'data');
	}

	/**
	 * 获取数据预处理器
	 * @return callable
	 */
	public function getDataPreprocessor()
	{
		return $this->dataPreprocessor;
	}

	/**
	 * 设置数据预处理器
	 * @param callable $dataPreprocessor
	 * @return void
	 */
	public function setDataPreprocessor(callable $dataPreprocessor)
	{
		$this->dataPreprocessor = $dataPreprocessor;
	}
}
